home *** CD-ROM | disk | FTP | other *** search
/ Games of Daze / Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso / x2ftp / msdos / source / swags-z / sorting.swg / 0003_ANAGRAM2.PAS.pas < prev    next >
Pascal/Delphi Source File  |  1993-05-28  |  16KB  |  414 lines

  1. { ANAGRAM. --------------------------------------------------------------------
  2.   Raphaël Vanney, 01/93
  3.  
  4.   Purpose : Reads a list of Words 4 to 10 Characters long from a File
  5.             named 'LIST.#1', outputs a list of anagrams founds in a
  6.             specified format to a File named 'ANAGRAM.RES'.
  7.  
  8.   Note    : I commented-out the source using a langage, say English, which
  9.             I'm not Really fluent in ; please forgive mistakes.
  10. ------------------------------------------------------------------------------}
  11.  
  12. {$m 8192,65536,655360}
  13. {$a+,d+,e-,f-,g+,i+,l+,n-,o-,q-,r-,s-,v+}
  14.  
  15. {$b-}     { Turns off complete Boolean evaluation ; this allows easiest
  16.             combined Boolean tests. }
  17.  
  18. Uses Crt,
  19.      Objects ;
  20.  
  21. Const
  22.      MaxWordLen     = 10 ;              { Offically specified by GP !      }
  23.      CntAnagrams    : Word = 0 ;        { Actually, this counter shows the }
  24.                                         { number of Words found in the     }
  25.                                         { output File.                     }
  26.      OutFileName    = 'ANAGRAM.RES' ;
  27.  
  28.  
  29. Type TWordString    = String[MaxWordLen] ;
  30.  
  31.      { TWordCollection.
  32.        This Object will be used to store the Words in a sorted fashion. As
  33.        long as the input list is already sorted, it could have inherited
  34.        from TCollection, put there is no big penalty using a sorted one.   }
  35.  
  36.      TWordCollection =
  37.      Object (TSortedCollection)
  38.           Function  KeyOf(Item : Pointer) : Pointer ; Virtual ;
  39.           Function  Compare(Key1, Key2 : Pointer) : Integer ; Virtual ;
  40.           Procedure FreeItem(Item : Pointer) ; Virtual ;
  41.      end ;
  42.      PWordCollection = ^TWordCollection ;
  43.  
  44.      { TWord.
  45.        This is the Object we'll use to store a Word. Each Word knows :
  46.        - it's 'Textual form'  : It
  47.        - the first of it's anagrams, if it has been found to be the
  48.          anagram of another Word,
  49.        - the next of it's anagrams, in the same condition.                 }
  50.  
  51.      PWord     = ^TWord ;
  52.      TWord     =
  53.      Object
  54.           It             : TWordString ;
  55.           FirstAng       : PWord ;
  56.           NextAng        : PWord ;
  57.  
  58.           Constructor    Init(Var Wrd  : TWordString) ;
  59.           Destructor     Done ;
  60.      end ;
  61.  
  62. Var  WordsList : PWordCollection ;      { The main list of Words           }
  63.      OrgMem    : LongInt ;              { Original MemAvail                }
  64.      UsedMem   : LongInt ;              { Amount of RAM used               }
  65.  
  66. {-------------------------------------- TWord --------------------------------}
  67.  
  68. Constructor TWord.Init ;
  69. begin
  70.      It:=Wrd ;
  71.      FirstAng:=Nil ;
  72.      NextAng:=Nil ;
  73. end ;
  74.  
  75. Destructor TWord.Done ;
  76. begin
  77. end ;
  78.  
  79. {-------------------------------------- TWordCollection ----------------------}
  80. { The following methods are not commented out, since they already are in
  81.   Turbo-Pascal's documentations, and they do nothing unusual.              }
  82.  
  83. Function TWordCollection.KeyOf ;
  84. begin
  85.      KeyOf:=Addr(PWord(Item)^.It) ;
  86. end ;
  87.  
  88. Function TWordCollection.Compare ;
  89. Var  k1   : PString Absolute Key1 ;
  90.      k2   : PString Absolute Key2 ;
  91. begin
  92.      If k1^>k2^
  93.      Then Compare:=1
  94.      Else If k1^<k2^
  95.           Then Compare:=-1
  96.           Else Compare:=0 ;
  97. end ;
  98.  
  99. Procedure TWordCollection.FreeItem ;
  100. begin
  101.      Dispose(PWord(Item), Done) ;
  102. end ;
  103.  
  104. {-------------------------------------- Utilities ----------------------------}
  105.  
  106. Procedure CleanUp(Var Wrd : TWordString) ;
  107. { Cleans-up a Word, in Case there would be dirty Characters in the input File }
  108. Var  i    : Integer ;
  109. begin
  110.      { Removes trailing spaces ; not afraid of empty Strings }
  111.      While Wrd[Length(Wrd)]=' ' Do Dec(Wrd[0]) ;
  112.      { Removes any suspect Character }
  113.      i:=1 ;
  114.      While (i<=Length(Wrd)) Do
  115.      begin
  116.           If Wrd[i]<#33 Then Delete(Wrd, i, 1)
  117.                         Else Inc(i) ;
  118.      end ;
  119. end ;
  120.  
  121. Function PadStr(St : TWordString ; Len : Integer) : String ;
  122. { Returns a String padded With spaces, of the specified length }
  123. Var  i    : Integer ;
  124.      Tmp  : String ;
  125. begin
  126.      Tmp:=St ;
  127.      For i:=Length(Tmp)+1 To Len Do Tmp[i]:=' ' ;
  128.      Tmp[0]:=Chr(Len) ;
  129.      PadStr:=Tmp ;
  130. end ;
  131.  
  132. {-----------------------------------------------------------------------------}
  133.  
  134. Function AreAnagrams(Var WordA, WordB : TWordString) : Boolean ;
  135. { Tells whether two Words are anagrams of each other ; assumes the Words
  136.   are 'clean' (No Up/Low Case checking, no dirty Characters...)
  137.  
  138.   Optimizing hint : Passing parameters by address _greatly_ enhances overall
  139.   speed ; anyway, we'll use a local copy of one of the two, since the used
  140.   algorithms needs to modify one of the two Words.                         }
  141.  
  142. Assembler ;
  143. Var  WordC     : TWordString ;          { Local copy of WordB              }
  144. Asm
  145.      Push DS                            { Let's save the Data segment...   }
  146.      LDS  SI, WordA                     { Load WordA's address in ES:DI    }
  147.      Mov  AL, [SI]                      { Load length Byte into AL         }
  148.      LDS  SI, WordB                     { Load WordB's address             }
  149.      Cmp  AL, [SI]                      { Compare lengthes                 }
  150.      JNE  @NotAng                       { <>lengthes, not anagrams         }
  151.  
  152.      LDS  SI, WordB
  153.  
  154.      { Let's make a local copy of WordB ; enhanced version of TP's "Move"  }
  155.      ClD                                { Clear direction flag             }
  156.      Push SS
  157.      Pop  ES                            { Segment part of WordC's address  }
  158.      LEA  DI, WordC                     { Offset part of it                }
  159.      Mov  CL, DS:[SI]                   { Get length Byte                  }
  160.      XOr  CH, CH                        { Make it a Word                   }
  161.      Mov  DL, CL                        { Save length For later use        }
  162.      Inc  CX                            { # of Bytes to store the String   }
  163.      ShR  CX, 1                         { We'll copy Words ; CF is importt }
  164.      Rep  MovSW                         { Copy WordB to WordC              }
  165.      JNC  @NoByte
  166.      MovSB                              { Copy last Byte                   }
  167. @NoByte:
  168.      LDS  SI, WordA                     { DS:SI contains WordA's address   }
  169.      Inc  SI                            { SI points to first Char of WordA }
  170.      Mov  DH, DL                        { Use DH as a loop counter         }
  171.      LEA  BX, WordC                     { Load offset of WordC in BX       }
  172.      Inc  BX                            { Skip length Byte                 }
  173.      { For each letter in WordA, search it in WordB ; if found, mark it as
  174.        'used' in WordB, then proceed With next.
  175.        If a letter is not found, Words are not anagrams ; if all are
  176.        found, Words are anagrams.                                          }
  177. { Registers usage :
  178.      AL        : scratch For SCAS
  179.      AH        : unused
  180.      BX        : offset part of WordC's address
  181.      CX        : will be used as a counter For SCAS
  182.      DL        : contains length of Strings ; 'll be used to reset CX
  183.      DH        : loop counter ; initially =DL
  184.      ES        : segment part of WordC's address
  185.      DI        : scratch For SCAS
  186.      DS:SI     : Pointer to next Char to process in WordA
  187. }
  188. @Bcle:
  189.      LodSB                              { Load next Char of WordA in AL    }
  190.      Mov  CL, DL                        { Load length of String in CX      }
  191.      Mov  DI, BX                        { Copy offset of WordC to DI       }
  192.      RepNE ScaSB                        { Scan WordC For AL 'till found    }
  193.      JNE  @NotAng                       { Char not found, not anagrams     }
  194.      Dec  DI                            { Back-up to matching Char         }
  195.      Mov  Byte Ptr ES:[DI], '*'         { Mark the Character as 'used'     }
  196.      Dec  DH                            { Dec loop counter                 }
  197.      Or   DH, DH                        { Done all Chars ?                 }
  198.      JNZ  @Bcle                         { No, loop                         }
  199.  
  200.      { All Chars done, the Words are anagrams                              }
  201.      Mov  AL, 1                         { Result=True                      }
  202.      Or   AL, AL                        { Set accordingly the ZF           }
  203.      Jmp  @Done
  204. @NotAng:
  205.      XOr  AL, AL                        { Result=False                     }
  206. @Done:
  207.      Pop  DS                            { Restore DS                       }
  208. end ;
  209.  
  210. Function ReadWordsFrom(FName : String) : Boolean ;
  211. Var  InF  : Text ;                      { Input File                       }
  212.      Buf  : Array[1..2048] Of Byte ;    { Speed-up Text buffer             }
  213.      Lig  : String ;                    { Read line                        }
  214.      Wrd  : String ;                    { Word gotten from parsed Lig      }
  215.      WSt  : TWordString ;               { Checked version of Wrd           }
  216.      p    : Integer ;                   { Work                             }
  217.      Cnt  : LongInt ;                   { Line counter                     }
  218. begin
  219.      ReadWordsFrom:=False ;             { 'till now, at least !            }
  220.      WordsList:=New(PWordCollection, Init(20, 20)) ;
  221.      Assign(InF, FName) ;
  222.      {$i-}
  223.      ReSet(InF) ;
  224.      {$i+}
  225.      If IOResult<>0 Then Exit ;
  226.      SetTextBuf(InF, Buf, SizeOf(Buf)) ;
  227.      Cnt:=0 ;
  228.  
  229.      While Not EOF(InF) Do
  230.      begin
  231.           Inc(Cnt) ;
  232.           ReadLn(InF, Lig) ;
  233.           While Lig<>'' Do
  234.           begin
  235.                { Let's parse the read line into Words }
  236.                p:=Pos(',', Lig) ;
  237.                If p=0 Then p:=Length(Lig)+1 ;
  238.                Wrd:=Copy(Lig, 1, p-1) ;
  239.                { Check of overflowing Word length }
  240.                If Length(Wrd)>MaxWordLen Then
  241.                     WriteLn('Word length > ', MaxWordLen, ' : ', Wrd) ;
  242.                WSt:=Wrd ;
  243.                CleanUp(WSt) ;
  244.                If WSt<>'' Then WordsList^.Insert(New(PWord, Init(WSt))) ;
  245.                Delete(Lig, 1, p) ;
  246.           end ;
  247.      end ;
  248.      {$i-}
  249.      Close(InF) ;
  250.      {$i+}
  251.      If IOResult<>0 Then ;
  252.      ReadWordsFrom:=True ;
  253.  
  254.      WriteLn(Cnt, ' lines, ', WordsList^.Count, ' Words found.') ;
  255. end ;
  256.  
  257. Procedure CheckAnagrams(i : Integer) ;
  258. { This Procedure builds, if necessary (i.e. not already done), the anagrams
  259.   list For Word #i of the list. }
  260. Var  Org  : PWord ;                     { Original Word (1st of list)      }
  261.      j    : Integer ;                   { Work                             }
  262.      Last : PWord ;                     { Last anagram found               }
  263. begin
  264.      Org:=WordsList^.Items^[i] ;
  265.      If Org^.FirstAng<>Nil Then
  266.      begin
  267.           { This Word is already known to be the anagram of at least another
  268.             one ; don't re-do the job. }
  269.           { _or_ this Word is known to have no anagrams in the list }
  270.           Exit ;
  271.      end ;
  272.  
  273.      { Search anagrams }
  274.      Last:=Org ;
  275.      Org^.FirstAng:=Org ;               { This Word is the first of it's   }
  276.                                         { own anagrams list ; normal, no ? }
  277.      For j:=Succ(i) To Pred(WordsList^.Count) Do
  278.      { Don't search the begining of the list, of course ! }
  279.      begin
  280.           { Let's skip anagram checking if lengths are <> }
  281.           If Org^.It[0]=PWord(WordsList^.Items^[j])^.It[0] Then
  282.           If AreAnagrams(Org^.It, PWord(WordsList^.Items^[j])^.It) Then
  283.           begin
  284.                { Build chained list of anagrams }
  285.                Last^.NextAng:=WordsList^.Items^[j] ;
  286.                Last:=WordsList^.Items^[j] ;
  287.                Last^.FirstAng:=Org ;
  288.           end ;
  289.      end ;
  290.      Last^.NextAng:=Nil ;               { Unusefull, but keep carefull     }
  291. end ;
  292.  
  293. Procedure ScanForAnagrams ;
  294. { This Procedure scans the list of Words For anagrams, and do the outputing
  295.   to the 'ANAGRAM.RES' File. }
  296.  
  297. Var  i         : Integer ;              { Work                             }
  298.      Tmp       : PWord ;                { Temporary Word                   }
  299.      Out       : Text ;                 { Output File                      }
  300.      Comma     : Boolean ;              { Helps dealing With commas        }
  301.      Current   : PWord ;                { Currently handled Word           }
  302. begin
  303.      Assign(Out, OutFileName) ;
  304.      ReWrite(Out) ;
  305.  
  306.      With WordsList^ Do
  307.      For i:=0 To Pred(Count) Do
  308.      begin
  309.           Current:=Items^[i] ;
  310.           CheckAnagrams(i) ;
  311.           { We're now gonna scan the chained list of known anagrams for
  312.             this Word. }
  313.           If (Current^.NextAng<>Nil) Or (Current^.FirstAng<>Current) Then
  314.           { This Word has at least an anagram other than itself }
  315.           begin
  316.                Write(Out, PadStr(Current^.It, 12)) ;
  317.                Inc(CntAnagrams) ;
  318.                Comma:=False ;
  319.                Tmp:=Current^.FirstAng ;
  320.                While Tmp<>Nil Do
  321.                begin
  322.                     If Tmp<>Current Then { Don't reWrite it... }
  323.                     begin
  324.                          If Comma Then Write(Out, ', ') ;
  325.                          Comma:=True ;
  326.                          Write(Out, Tmp^.It) ;
  327.                          Inc(CntAnagrams) ;
  328.                     end ;
  329.                     Tmp:=Tmp^.NextAng ;
  330.                end ;
  331.                WriteLn(Out) ;
  332.           end ;
  333.      end ;
  334.  
  335.      Close(Out) ;
  336. end ;
  337.  
  338. Var  Tmp       : LongInt ;
  339.  
  340. begin
  341.   { Check command line parameter }
  342.  
  343.   If ParamCount<>1 Then
  344.   begin
  345.     WriteLn('Anagram. Raphaël Vanney, 01/93 - Anagram''s contest entry.');
  346.     WriteLn ;
  347.     WriteLn('Anagram <input_File>') ;
  348.     WriteLn ;
  349.     WriteLn('Please specify input File name.') ;
  350.     Halt(1) ;
  351.   end ;
  352.  
  353.   OrgMem:=MemAvail ;
  354.  
  355.   { Read Words list from input File }
  356.  
  357.   If Not ReadWordsFrom(ParamStr(1)) Then
  358.   begin
  359.        WriteLn('Error reading Words from input File.') ;
  360.        Halt(1) ;
  361.   end ;
  362.  
  363.   { Display statistics stuff }
  364.  
  365.   WriteLn('Reading and sorting done.') ;
  366.   UsedMem:=OrgMem-MemAvail ;
  367.   WriteLn('Used RAM                       : ', UsedMem, ' Bytes') ;
  368.   Tmp := Trunc(1.0 * MemAvail / (1.0 * UsedMem / WordsList^.Count)) ;
  369.   If Tmp > 16383 Then
  370.     Tmp := 16383 ;
  371.   WriteLn('Potential Words manageable     : ', Tmp) ;
  372.  
  373.   { Scan For anagrams, create output File }
  374.  
  375.   ScanForAnagrams ;
  376.   WriteLn('Anagrams scanning & output done.') ;
  377.   WriteLn(CntAnagrams, ' Words written to ', OutFileName) ;
  378.  
  379.   { Clean-up }
  380.   Dispose(WordsList, Done) ;
  381. end.
  382. {
  383.  
  384. ------------------------------------------------------------------------------
  385.  
  386. Okay, this is my entry For the 'anagram contest' !
  387.  
  388. The few things I'd like to point-out about it :
  389.  
  390. . I chosed to use OOP, in contrast to seeking speed. I wouldn't say my
  391.   Program is Really slow (7.25 secs on my 386-33), but speed was not my
  392.   first concern.
  393. . It fully Uses one of the interresting points of OOP in TP, i.e.
  394.   reusability, through inheritance,
  395. . When a Word (A) has been found to be an anagram of another (B), the
  396.   Program never searches again For the anagrams of (A) ; this
  397.   highly reduces computing time... but I believe anybody does the same.
  398. . I also quite like the assembly langage Function 'AreAnagrams'.
  399.  
  400. ------------------------------------------------------------------------------
  401.  
  402. The Words list is stored in memory in the following maner :
  403. . A collection (say, a list) of the Words,
  404. . Within this list, anagrams are chained as a list
  405. . Each Word knows the first and the next of its anagrams
  406.  
  407. ------------------------------------------------------------------------------
  408.  
  409. For the sake of speed, I did something I'm quite ashamed of ; but it
  410. saves 32% of execution time, so...
  411. The usual way to access element #i of a TCollection is to call Function At
  412. with parameter i (i.e. At(i)) ; there is also another way, which is not Really
  413. clean, but which I chosed to use : access it directly through Items^[i].
  414.